

#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include "mythread.h"

/* Used to restore original signal once our read call's work is complete */
struct sigaction old_handler;
/* Timer value before we installed our own timer */
struct itimerval old_timer;
/* Mask value before we installed our own mask */
sigset_t old_mask;

/* Indicates if a thread needs to be rescheduled */
int rescheduleFlag = FALSE;

/**
 * Handle SIGVTALRM by calling the scheduler when necessary and performing time slicing
 *
 *@param sig  The signal that is being handled
 */
void mythread_timer_handle(int sig);

/**
 * mythread_init_sched - called once at the start of each thread
 * enable virtual timer signals, install signal handler for it and
 * set time slice to 20ms for virtual timer
 */
void mythread_init_sched(void){
	/* printf("Entering the init!\n"); */

	/* Set the handler for SIGVTALRM to be mythread_timer_handle and set the handler to block SIGVTALRM while it is operating */
	struct sigaction new_handler;
	new_handler.sa_handler = mythread_timer_handle;
	sigemptyset(&new_handler.sa_mask);
	sigaddset(&new_handler.sa_mask, SIGVTALRM);
	if(sigaction(SIGVTALRM, &new_handler, &old_handler) == -1){
		printf("Sigaction FAILED on init!\n");
	}
	
	/* Unmask SIGVTALRM for this thread */
	sigset_t new_mask;
	sigemptyset(&new_mask);
	sigaddset(&new_mask, SIGVTALRM);
	if(sigprocmask(SIG_UNBLOCK, &new_mask, &old_mask) != 0){
		printf("Sigprocmask FAILED on init!\n");
	}
	
	/* setitimer() to generate signals at the time-slice intervals */
	struct itimerval new_timer;
	new_timer.it_interval.tv_sec = 0;
	new_timer.it_interval.tv_usec = 20000;
	new_timer.it_value.tv_sec = 0;
	new_timer.it_value.tv_usec = 20000;
	if(setitimer(ITIMER_VIRTUAL,&new_timer,&old_timer) == -1){
		printf("Setitimer FAILED on init!\n");
	}
}

/**
 * mythread_exit_sched - called on thread terminiation (exit) by each thread
 * disable all the features activated in mythread_init_sched()
 *
 */
void mythread_exit_sched(void){
	/* Reset the original SIGVTALRM signal handler
	if(sigaction(SIGVTALRM, &old_handler, NULL) == -1){
		printf("Sigaction FAILED on exit!\n");
	}*/
	
	/* Remask SIGVTALRM for this thread */
	sigset_t new_mask;
	sigemptyset(&new_mask);
	sigaddset(&new_mask, SIGVTALRM);
	if(sigprocmask(SIG_BLOCK, &new_mask, NULL) != 0){
		printf("Sigprocmask FAILED on exit!\n");
	}
	
	/* Reset the original ITIMER_VIRTUAL timer
	errno = 0;
	if(setitimer(ITIMER_VIRTUAL, &old_timer, NULL) == -1){
		perror("Setitimer FAILED on exit!\n");
	}*/
}

/**
 * mythread_leave_kernel - leave the monolithic threading kernel w/ preemptions
 */
void mythread_leave_kernel(void){
	/* Start simple, just leave the kernel */
	mythread_leave_kernel_nonpreemptive();
	
	while(rescheduleFlag && mythread_tryenter_kernel()){
		/* printf("About to call schedule in leave_kernel!!!!\n"); */
		mythread_schedule();
		
		mythread_t currentThread = mythread_self();
		if ((mythread_lenq(mythread_readyq()) > 0) && (currentThread->reschedule == 1) && (((currentThread->state) & MYTHREAD_RUN) == MYTHREAD_RUN) && (mythread_inq(mythread_runq(),(void *) currentThread))) {
			currentThread->reschedule = 0;
			/* printf("Calling block in leave_kernel!!!!\n"); */
			mythread_block(mythread_readyq(), 0);
        } else {
            mythread_leave_kernel_nonpreemptive(); 
        }
	}
}



/**
 * Handle SIGVTALRM by calling the scheduler when necessary and performing time slicing
 *
 *@param sig  The signal that is being handled
 */
void mythread_timer_handle(int sig){
	/* Check the current threads state to be sure that it is running, otherwise return */
	mythread_t signalledThread = mythread_self();
	if(((signalledThread->state) & MYTHREAD_RUN) != MYTHREAD_RUN){
		/* printf("Non-running thread received SIGVTALRM!!!!\n"); */
		return;
	}

	/* Try to enter the kernel and if unsuccessful sets flag indicating a need to be called later */
	int success = mythread_tryenter_kernel();
	if(success == 0){
		/* printf("Failed to enter handler upon SIGVTALRM!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); */
		int numRunning = mythread_lenq(mythread_runq());
		/* printf("Length of run queue is: %d\n", numRunning); */
		mythread_t currentThread = mythread_self();
		currentThread->reschedule = 1;
		rescheduleFlag = TRUE;
		return;
	} else{
		/* Invoke the scheduler */
		/* printf("Calling schedule in handler!\n"); */
		mythread_schedule();
		
		/* Block_thread/reschedule the current thread if the ready queue is not empty */
		mythread_t currentThread = mythread_self();
		if((mythread_lenq(mythread_readyq()) > 0) && (((currentThread->state) & MYTHREAD_RUN) == MYTHREAD_RUN)){
			if(mythread_inq(mythread_runq(),(void *) currentThread)){
				/* printf("Calling block on current thread in handler!!!!\n"); */
				currentThread->reschedule = 0;
				currentThread->dprio = currentThread->dprio + 10;
				mythread_block(mythread_readyq(), 0);
			}
		} else{
			/* Leave the kernel, handle other threads that need to be rescheduled */
			mythread_leave_kernel();
		}
		
		return;
	}
}

/**
 * Scheduler function that will reschedule threads based on new priorities
 *
 */
void mythread_schedule(void){	
	int numRunning = mythread_lenq(mythread_runq());
	/* printf("Length of run queue is: %d\n", numRunning); */
	
	/* Increase the priority of all threads in the ready queue by 1 (subtract 1 from dprio) */
	int numReady = mythread_lenq(mythread_readyq());
	/* printf("Length of ready queue is: %d\n", numReady); */
	
	mythread_queue_t readyQueue = *(mythread_readyq());
	mythread_queue_t queueNext;
	
	mythread_t currentThread;
	
	int i;
	for(i = 0;i < numReady;readyQueue = queueNext){
		/* Go ahead and set next queue pointer */
		queueNext = readyQueue->next;
			
		currentThread = readyQueue->item;
		currentThread->dprio = currentThread->dprio - 1;
			
		i++;
	}
	
	if(rescheduleFlag){
		rescheduleFlag = FALSE;
	
		if(mythread_lenq(mythread_readyq()) > 0){
			/* Check threads for set reschedule flags and take action (except for the running thread) */		
			mythread_t runningThread = mythread_self();
			
			mythread_queue_t runQueue = *(mythread_runq());
		
			for(i = 0;i < numRunning;runQueue = queueNext){
				/* Go ahead and set next queue pointer */
				queueNext = runQueue->next;
			
				currentThread = runQueue->item;
			
				if((currentThread->reschedule == 1) && (currentThread != runningThread) && (((currentThread->state) & MYTHREAD_RUN) == MYTHREAD_RUN)){
					currentThread->reschedule = 0;
					currentThread->dprio = currentThread->dprio + 10;
					/* printf("Calling block_thread in schedule!!!!\n"); */
					mythread_block_thread(currentThread, mythread_readyq(), 0);
				}
			
				i++;
			}
		}
	}
	
	return;
}

/**
 * mythread_attr_setschedparam - set the value of scheduling
 * parameters ('sched_priority') in the attribute,
 * to define/change the thread priority, 
 * see /usr/include/linux/sched.h for struct sched_param
 */
int mythread_attr_setschedparam (mythread_attr_t *attr, struct sched_param *param){
	attr->param = *param;
	return 1;
}

/**
 * mythread_attr_getschedparam - return the value of the scheduling
 * parameters ('sched_priority') from the attribute
 */
int mythread_attr_getschedparam (const mythread_attr_t *attr, struct sched_param *param){
    return attr->param.__sched_priority;
}
